﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;

using OpGen.Extensions.Runtime.Client;
using OpGen.Extensions.Security;

namespace OpGen.Extensions.Network
{
    /// <summary>
    /// Stellt einen Client das der mit dem Server verbunden ist
    /// </summary>
    public abstract class ServerClientSocket : ClientMessageMethodInvoker, IConnection
    {
        private Socket _socket;
        private Guid _id = Guid.NewGuid();

        /// <summary>
        /// Wird ausgelöst wenn der Client die Verbindung unterbricht
        /// </summary>
        public event DisconnectDelegate ClientDisconnect;

        /// <summary>
        /// Wird aufgelöst wenn Daten vom Client empfangen wurden
        /// </summary>
        public event ReceiveDelegate ClientReceive;

        private byte[] _buffer = new byte[4096];
        private string _remoteIPAddress;
        private BufferStream _recvBuffer = new BufferStream();
        private BufferStream _sendBuffer = new BufferStream();

        /// <summary>
        /// Erstellt eine neue Instanz der ServerClientSocket Klasse
        /// </summary>
        protected ServerClientSocket()
        {
            _remoteIPAddress = "N/A";
            OnRegisterOpCode();
        }

        /// <summary>
        /// Erstellt eine neue Instanz der ServerClientSocket Klasse
        /// </summary>
        /// <param name="socket">Der Socket der die Client Verbindung dar stellt</param>
        internal ServerClientSocket(Socket socket)
        {
            _socket = socket;
            _remoteIPAddress = ((IPEndPoint)_socket.RemoteEndPoint).Address.ToString();

            OnRegisterOpCode();

            _socket.BeginReceive(_buffer, 0, 4096, SocketFlags.None, InternalReceiveCallback, null);
        }

        private void InternalReceiveCallback(IAsyncResult ar)
        {
            try
            {
                var length = _socket.EndReceive(ar);
                if (length == 0) { Close(); return; };

                if (_recvBuffer.IsClean)
                {
                    var totalLength = BitConverter.ToInt32(_buffer, 0);

                    _recvBuffer.Begin(_buffer, 4, length - 4, totalLength);
                }
                else
                {
                    _recvBuffer.Append(_buffer, 0, length);
                }

                if (_recvBuffer.IsComplet)
                {
                    var recvArray = _recvBuffer.ToArray();

                    if (CryptoKey != null)
                        RC4Crypt.RC4(ref recvArray, CryptoKey);

                    OnClientReceive(recvArray);
                    _recvBuffer.Clear();
                }

                if (_socket != null && _socket.Connected)
                    _socket.BeginReceive(_buffer, 0, 4096, SocketFlags.None, InternalReceiveCallback, null);

            }
            catch (Exception)
            {
                Close();
                return;
            }
        }

        /// <summary>
        /// Liefert eine ID die diesen Client identifiziert
        /// </summary>
        public Guid ID
        {
            get { return _id; }
        }

        /// <summary>
        /// Liefert die IP Adresse des Clients
        /// </summary>
        public string RemoteIPAddress
        {
            get { return _remoteIPAddress; }
        }

        /// <summary>
        /// Trennt die Verbindung und löst ein Disconnect aus
        /// </summary>
        public void Close()
        {
            if (_socket != null && _socket.Connected)
                _socket.Shutdown(SocketShutdown.Send);

            _socket = null;

            OnClientDisconnect();
        }

        /// <summary>
        /// Sendet die Daten an den Client
        /// </summary>
        /// <param name="data"></param>
        public void Send(byte[] data)
        {
            _sendBuffer.Clear();
            _sendBuffer.Append(BitConverter.GetBytes(data.Length), 0, 4);
            _sendBuffer.Append(data, 0, data.Length);

            if (_socket != null && _socket.Connected)
            {
                if (CryptoKey == null)
                    _socket.Send(_sendBuffer.ToArray(), SocketFlags.None);
                else
                { 
                    var sendArray = _sendBuffer.ToArray();
                    RC4Crypt.RC4(ref sendArray, CryptoKey);
                    _socket.Send(sendArray, SocketFlags.None);
                }
            }
        }

        /// <summary>
        /// Gibt an ob die Verbidung noch besteht
        /// </summary>
        public bool IsConnected
        {
            get { return _socket != null && _socket.Connected; }
        }

        /// <summary>
        /// Gibt den privaten Verschlüsselungsschlüssel an
        /// </summary>
        public byte[] CryptoKey { get; set; }

        /// <summary>
        /// Gibt an ob die Übermittlung verschlüsselt ist
        /// </summary>
        public bool IsAuthenticated { get { return CryptoKey != null; } }

        /// <summary>
        /// Wird aufgerufen wenn Daten vom Client eingetroffen sind
        /// </summary>
        /// <param name="data">Die Daten die empfangen wurden</param>
        protected virtual void OnClientReceive(byte[] data)
        {
            if (ClientReceive != null) ClientReceive.Invoke(this, data);
        }

        /// <summary>
        /// Wird aufgerufen wenn der Client die Verbindung unterbricht
        /// </summary>
        protected virtual void OnClientDisconnect()
        {
            if (ClientDisconnect != null) ClientDisconnect.Invoke(this);
        }

        /// <summary>
        /// Wird aufgerufen um OpCode Handler zu registrieren
        /// </summary>
        protected virtual void OnRegisterOpCode()
        {
            // Nix machen
        }
    }
}
